#include "gui.h"
#include "GEdit.h"
#include "Graphics.h"
#include "utils/utils.h"
#include <time.h>

#include <assert.h>
#include "scintilla/ILexer.h"
#include "scintilla/Scintilla.h"
#include "scintilla/SplitVector.h"
#include "scintilla/Partitioning.h"
#include "scintilla/RunStyles.h"
#include "scintilla/CellBuffer.h"
#include "scintilla/PerLine.h"
#include "scintilla/CharClassify.h"
#include "scintilla/CharacterSet.h"
#include "scintilla/Decoration.h"
#include "scintilla/Document.h"
#include "scintilla/RESearch.h"
#include "scintilla/UniConversion.h"
#include "scintilla/Document.h"
//#include "scintilla/Scintilla.cpp"


GEdit::GEdit(GForm *parent /* = 0 */)
:GView(parent)
,m_pCaret(0)
,m_ptCaretDocPos(-1)
,m_topLine(0)
,m_pDoc(0)
,m_pGraphics(0)
,margin_LineNumber(1)
,margin_BookMark(1)
,margin_Fold(1)
//,tab_on(1)
//,tab_size(4)
{
    ShortCut  sc;
    sc.setCtrl();
    sc.setChar('A');
    m_shortcutMaper[sc.getValue()] = GFE_SELECTALL;
    sc.setChar('C');
    m_shortcutMaper[sc.getValue()] = GFE_COPY;
    sc.setChar('V');
    m_shortcutMaper[sc.getValue()] = GFE_PASTE;
    sc.setChar('X');
    m_shortcutMaper[sc.getValue()] = GFE_CUT;
    sc.setChar('Y');
    m_shortcutMaper[sc.getValue()] = GFE_REDO;
    sc.setChar('Z');
    m_shortcutMaper[sc.getValue()] = GFE_UNDO;
    
    m_pGraphics = new Graphics(this);
    GFont& font = m_pGraphics->getFont();
    
    m_pCaret = new GCaret(this, 1, font.getHeight());
    setLineHeight(font.getHeight());
    setCharWidth(font.getWidth());
    GView::setViewScroll(HSCROLL, 0, 1000);
    //Document
    if (m_pDoc == 0)
    {
        m_pDoc = new Document();
        m_pDoc->tabInChars = 4;
        m_pDoc->useTabs = false;
    }
    setCaretAt(0);
    
}

GEdit::~GEdit(void)
{
    if (m_pCaret)
    {
        delete m_pCaret;
        m_pCaret = 0;
    }
    if (m_pDoc){
        delete m_pDoc;
        m_pDoc = 0;
    }
}

//////////////////////////////////////////////////////////////////////////
void GEdit::selectLines(int startLine, int endLine)
{
    int sl = startLine;
    int el = endLine;
//     if (sl > el)
//     {
//         sl = endLine;
//         el = startLine;
//     }
    if (sl < 0 ) sl = 0;
    if (el > totalLines() - 1) el = totalLines() - 1;
    int spos = 0; //m_pDoc->LineStart(sl)
    int epos = 0; //m_pDoc->LineEnd(el)
    
}

bool GEdit::inSelection(int docPos) const
{
    if (docPos == -1 || m_selection.isEmpty()) return false;
    if (!m_selection.contains(docPos)) return false;

    return true;
}

void GEdit::selectWord(int docPos)
{
    
}

void GEdit::moveCaret(CaretMovement move)
{
    int pos = caretDocPos();
    switch (move)
    {
    case UP:
        {
            
        }
        break;
    case DOWN:
        {

        }
        break;
    case LEFT:
        {

        }
        break;
    case RIGHT:
        {

        }
        break;
    }
    setCaretAt(pos);
}

int GEdit::getMargin(int type, long param /* = 0 */) const
{
    int offset = 0;
    switch (type)
    {
    case LineNumber:
        if (margin_LineNumber )
        {
            int xchars = 4; // '9999'
            int limit = 9999;
            int total = totalLines();
            while (total > limit) // m_pDoc->LinesTotal() > limit
            {
                limit = limit * 10 + 9;
                xchars++;
            }
            offset += xchars * m_pGraphics->getFont().getCharSize('9').w; 
        }
        break;
    case Margin:
        if (margin_LineNumber)
        {
            offset = 3;
        }
        break;
    case BookMark:
        if (margin_BookMark)
        {
            offset += lineHeight();
        }
        break;
    case Folder:
        if (margin_Fold)
        {
            offset += 12;
        }
    }
    return offset;
}

GRect GEdit::marginsArea()const 
{
     int w = 0;
     GRect rc;
     w += getMargin(LineNumber);
     w += getMargin(Margin);
     w += getMargin(BookMark);
     w += getMargin(Folder);
     rc.setWidth(w);
     rc.setHeight(GView::getClientSize().h);
     return rc;
}

GRect GEdit::textArea() const
{
    GSize sz(getClientSize());
    GRect rc(marginsArea());
    GRect text;
    text.setWidth(sz.w);
    text.setHeight(sz.h);
    text.left += rc.width();
    return text;
}

void GEdit::gotoLine(int docLine) 
{
    int d = m_topLine < docLine ? 1 : -1;
    
    if (!isVisible(docLine))
    {
        //expand
    }
    int total = totalLines();
    int count = 0;
    int t = m_topLine;
    for (  t = m_topLine + d; t != docLine; t += d)
    {
        if (isVisible(t)) count += d;
        if (t < 0){ t = 0; break; }
        if (t >= total) { t = total - 1; break;}
    }
    GScrollEvent e;
    e.counts = totalVisualLines();
    e.type = VSCROLL;
    e.oldPos = GView::getVertPos();
    e.pos = e.oldPos + count;
    scrollEvent(e);
    m_topLine = t;
    repaint();
}
//////////////////////////////////////////////////////////////////////////

void GEdit::mousePressEvent( GMouseEvent& event )
{
	setCursor(GCursor::Text);
    setFocus(true);
    uint  mouse = event.buttons();
    const GPoint &point = event.m_ptPos;
    GRect rcMargin = marginsArea();
    if (rcMargin.contains(point))
    {
        dprint("GEdit::mousePressEvent: in marginsArea(%d,%d,%d,%d)!\n", rcMargin.left,rcMargin.top,rcMargin.right,rcMargin.bottom);
        if (mouse  == (LeftButton | CtrlKey))
        {
            selectAll();
            return;
        }
        if ((mouse & LeftButton) )  //line selection
        {
            int selline = 0;//m_pDoc->getDocPos(m_selection.startPso());
            int startline = 0;
            int endline = documentLine(point);
            if (mouse & ShiftKey)// if not tap shift select single line
            {
                if (m_selection.isEmpty())  // select mutiple lines
                {
                    startline = caretDocLine();
                }else {
                    startline = selline;
                }
            }else {
                startline = endline ;  //select single line
            }
            
            selectLines(startline, endline);
            m_selType = Line;
            return;
        }
        return;
    }

    // in text area
    GRect rcText = textArea();
    if (!rcText.contains(point)) return;

    dprint("GEdit:point(%d,%d) pressdown in TextArea(%d,%d,%d,%d)\n", point.x, point.y, rcText.left, rcText.top, rcText.right, rcText.bottom);
    int docPos = documentPos(point);
    bool ptInselection = inSelection(docPos);
    //int docLine = screenLine(docPos);
    m_drapState = None;
    switch (mouse)
    {
    case LeftButton:  //single click 
        {
            if (ptInselection) // could drag
            {
                m_drapState = Begin;
            }else{
                m_selection.set(docPos); //cancel selections
                m_selType = Char;
                setCaretAt(docPos);
                repaint();
            }
            
        }
        return ;
    case LeftButton | ShiftKey:   //select mutile lines rectangle
        {
            m_drapState = None;
            if (!m_selection.isEmpty())
            {
                m_selection.setEnd(docPos);
            }
            else 
            {
                m_selection.setStart(m_ptCaretDocPos);
                m_selection.setEnd(docPos);
            }
            setCaretAt(docPos);
            m_selType = Char;
            repaint();
        }
        return;
    case LeftButton | CtrlKey:  // select single word
        {
            m_drapState = None;
            m_selType = Word;
            selectWord(docPos);
        }
        break;
    }

    
}
void GEdit::mouseMoveEvent( GMouseEvent& event )
{
    setCursor(GCursor::Text);
    uint mouse = event.buttons();
    if (!(event.buttons() & LeftButton)) return;

    const GPoint& point = event.m_ptPos;
    GRect rcText = textArea();
    bool bInText = rcText.contains(point);
    if (!bInText)
        return ;
    int movePos = documentPos(point);
    if (m_drapState == Begin) //drap selection action
    {   
        return;
    }
    
    switch (m_selType)
    {
    case Char:
        {
            { //when dragging mouse to select view scrolled
                int pos = GView::getHorzPos();
                const int delta = lineHeight() ;
                if (point.x < rcText.left + delta && pos > 0) 
                    GView::scrollTo(HSCROLL, pos - 1);
                else if (point.x > rcText.right - delta && pos < GView::getHorzRange()) 
                    GView::scrollTo(HSCROLL, pos + 1);

                pos = GView::getVertPos();
                if (point.y < rcText.top + delta && pos > 0) 
                    m_topLine = visualDocline(topLine(), -1);
                else if (point.y > rcText.bottom - delta && pos < GView::getVertRange()) 
                    m_topLine = visualDocline(topLine(), 1);
            }
            m_selection.setEnd(movePos);
            setCaretAt(movePos);
            
        }
        break;
    case Word:
        {

        }
        break;
    case Line: //continue to select line no matter if shift is down
        {
            int startline = m_selection.startPos();
            int endline = documentLine(point);
            selectLines(startline, endline);
        }
        break;
    }
    repaint();
}
void GEdit::mouseReleaseEvent( GMouseEvent& event )
{
    setCursor(GCursor::Text);
    const GPoint &point = event.m_ptPos;
    int docPos = documentPos(point);
    switch (m_drapState)
    {
    case Begin: // did not move test block
        m_drapState = None;
        m_selection.set(docPos);
        setCaretAt(docPos); // caret should be set at last point (in selected text but not move text block)
        repaint();
        break;
    case Draging:
        m_drapState = None;
        break;
    default:
        break;
    }

}


void GEdit::mouseDoubleClickEvent( GMouseEvent& event )
{
    setCursor(GCursor::Text);
    setFocus(true);

    uint mouse = event.buttons();
    GPoint& point = event.m_ptPos;
    int docPos = documentPos(point);
    m_drapState = None;
    m_selType = Word;
    selectWord(docPos);
}

void GEdit::wheelEvent( GWheelEvent& event )
{
    setFocus(true);
    int delta = event.m_Direct < 0 ? 3 : -3;
    if (event.m_Flag == CtrlKey)
    {
        zoom(delta);
        return ;
    }

    GView::wheelEvent(event);

    int top = visualDocline(topLine(), delta);

    if (delta < 0 && top != m_topLine || delta > 0 && bottomLine() != totalLines() - 1  )
    {
        m_topLine = top;
        repaint();
    }
    
}

void GEdit::scrollEvent( GScrollEvent& event )
{
    GView::scrollEvent(event);

    if (event.type == VSCROLL)
        m_topLine = visualDocline(m_topLine, event.pos - event.oldPos);
}


void GEdit::itemEvent(int itemID, long param)
{
    switch (itemID)
    {
    case GFE_COPY:   copy();    break;
    case GFE_PASTE:  paste();   break;
    case GFE_CUT:    cut();     break;
    case GFE_SELECTALL: selectAll();  break;
    case GFE_UNDO:   undo();    break;
    case GFE_REDO:   redo();    break;
    }
}

void GEdit::dropEvent(const GDropEvent& event)
{
    for (size_t i = 0 ; i < event.size(); i++)
    {
        GString fileName = event[i];
        dgprint(GTEXT("Drop File:%s\n"), fileName.c_str());
    }
}

void GEdit::charPressEvent(wchar_t unichar, void* param) //independent from gchar
{
    GRect invalidRect;
    const wchar_t* pw = 0;
    GSize clinet = getClientSize();

    int selPos = 0, sellen = 0;
    bool repaintAll = false;
    if (!m_selection.isEmpty()){
        
        if (m_selection.startPos() <  m_selection.endPos())
        {
            selPos = m_selection.startPos(); sellen = m_selection.endPos() - m_selection.startPos();
        }else{
            selPos = m_selection.endPos(); sellen = m_selection.startPos() - m_selection.endPos();
        }
        repaintAll = true; //selection would be canceled
    }
    switch(unichar)
    {
    case 0x08:  //Back
        {
            do{
                if (repaintAll){
                    m_selection.set(selPos);
                    break;
                }
                if (m_ptCaretDocPos == 0)
                    break;
                int line = m_pDoc->LineFromPosition(m_ptCaretDocPos);
                int linehead = m_pDoc->LineStart(line);
                int linetail = 0;
                if (m_ptCaretDocPos == linehead) //m_ptCaretDocPos is line head but not first line
                {
                    int lastlinetail = m_pDoc->LineEnd(line - 1);
                    selPos = lastlinetail;
                    sellen = m_ptCaretDocPos - lastlinetail;
                }else{ //delete one character (including not-ascii chars)
                    linetail = m_pDoc->LineEnd(line);
                    char linestr[1024];
                    m_pDoc->GetCharRange(linestr, linehead, linetail - linehead);
                    const char * prev = last_character(linestr, linestr + m_ptCaretDocPos - linehead);
                    selPos = linehead + prev - linestr;
                    sellen = linestr - prev + m_ptCaretDocPos - linehead;
                }
            }while(0);

            if (selPos >= 0 && sellen > 0){
                m_pDoc->DeleteChars(selPos, sellen);
                m_ptCaretDocPos = selPos;
            }
        }
        break;
    case 0x09: // Tab
        {
            if (repaintAll)
            {
                m_pDoc->DeleteChars(selPos, sellen);
                m_ptCaretDocPos = selPos;
            }
            char buf[64];
            int len = 0;
            bool tab_on = m_pDoc->useTabs;
            if (tab_on){
                buf[len++] = '\t';
            }else{
                int tab_size = m_pDoc->tabInChars;
                for (int i = 0 ; i < tab_size; i++)
                {
                    buf[len++] = ' ';
                }
            }
            m_pDoc->InsertString(m_ptCaretDocPos, buf, len);
            m_ptCaretDocPos += len;
            m_selection.set(m_ptCaretDocPos);
            if (!repaintAll)
            {
                GPoint pt = screenPoint(m_ptCaretDocPos);
                invalidRect.translate(pt.x, pt.y);
                invalidRect.setHeight(lineHeight());
                invalidRect.setWidth(getClientSize().w - pt.x);
            }
        }
        break;
    case 0x0D:  // \r\n   
        {
            if (repaintAll)
            {
                m_pDoc->DeleteChars(selPos, sellen);
                m_ptCaretDocPos = selPos;
            }
            char buf[256] = "\r\n";
            int len = 0;
            switch (m_pDoc->eolMode)
            {
            case SC_EOL_CRLF:   len += 2; break;
            case SC_EOL_CR:     len++; break;
            case SC_EOL_LF:     buf[len++]='\n';break;
            }
            buf[len]=0;
            m_pDoc->InsertString(m_ptCaretDocPos, buf, len);
            int lastpos = m_ptCaretDocPos;
            m_ptCaretDocPos += len;
            m_selection.set(m_ptCaretDocPos);

            int lastline = m_pDoc->LineFromPosition(lastpos);
            int curline = m_pDoc->LineFromPosition(m_ptCaretDocPos);
            int indent = m_pDoc->GetLineIndentation(lastline);
            m_pDoc->SetLineIndentation( curline, indent);
            m_ptCaretDocPos = m_pDoc->GetLineIndentPosition(curline);
            if (!repaintAll)
            {
                GPoint pt = screenPoint(m_ptCaretDocPos);
                invalidRect.translate(pt.x, pt.y);
                invalidRect.setHeight(getClientSize().w - pt.y);
                invalidRect.setWidth(getClientSize().w - pt.x);
            }
            
            break;
        }
    default:
        {
            if (repaintAll )
            {
                m_pDoc->DeleteChars(selPos, sellen);
                m_ptCaretDocPos = selPos;
            }
            char str[1024];
            int len = ucs2_ascii(&unichar, 1, str);
            m_pDoc->InsertString(m_ptCaretDocPos, str, len);
            m_ptCaretDocPos += len;
            m_selection.set(m_ptCaretDocPos);
            if (!repaintAll)
            {
                GPoint pt = screenPoint(m_ptCaretDocPos);
                invalidRect.translate(pt.x, pt.y);
                invalidRect.setHeight(lineHeight());
                invalidRect.setWidth(getClientSize().w - pt.x);
            }
            break;
        }
    }

    repaint(invalidRect);
}

void GEdit::paintEvent( GPaintEvent& event )
{
    GRect rcPaint(event.m_rcPaint);
//    dprint("GEdit::paintEvent:paintRC=(%d,%d,%d,%d)\n", rcPaint.left,rcPaint.top, rcPaint.right,rcPaint.bottom);
    GSize sz = getClientSize();
    Graphics mem(*m_pGraphics, sz);
    Graphics* pgraph = &mem;

    pgraph->fillRect(GRect(0,0, sz.w, sz.h), GColor(255,255,255));
    //draw margins
    GRect rcMargin(marginsArea());
    pgraph->fillRect(rcMargin, GColor(192,192,192));

    TEST_FUNC(_drawMargin(pgraph));
    //draw selections
    TEST_FUNC(_drawSelection(pgraph));

    //show caret for scroll event
    {
        GPoint pt = screenPoint(m_ptCaretDocPos);
        if (textArea().contains(pt))
            m_pCaret->setPos(pt);
        else {
            m_pCaret->hide();
        }
    }
    

    //draw text
    int docline = topLine();
    int total = totalLines();
    int visualLine = 0;
    int counts = GView::getVLinesOnScreen();
    GPoint tmp(0,0);

    while (visualLine < counts)
    {
        docline = documentLine(tmp);
        _drawLine(pgraph, docline, visualLine);
        if (docline == total - 1) break;
        tmp.y += lineHeight();
        visualLine++;
    }
    m_pGraphics->copy(pgraph);
}

void GEdit::_drawMargin(Graphics* pGraph) const
{
    GRect rc(marginsArea());
    int x = 0;
    rc.setWidth(getMargin(LineNumber) + getMargin(Margin));
    pGraph->fillRect(rc, GColor(192,192,192));
    rc.left = rc.right + 1;
    rc.setWidth(getMargin(BookMark));
    pGraph->fillRect(rc, GColor(192,192,192));
    rc.left = rc.right + 1;
    rc.setWidth(getMargin(Folder));
    pGraph->fillRect(rc, GColor(235,235,235));
    rc.left = rc.right + 1;
}

void GEdit::_drawSelection(Graphics* pGraph) const
{
    if (m_selection.isEmpty()) return;

    int start = m_selection.startPos();
    int end = m_selection.endPos();
    if (start > end)
    {
        int tmp = start;
        start = end;
        end = tmp;
    }

    int startDocline = m_pDoc->LineFromPosition(start); // ;  Document::getLine(int pos)
    int endDocline = m_pDoc->LineFromPosition(end) ; // ;

    int s = 0;
    int e = 0;
/*
    while (startScrline <= endScrline)
    {
        int linehead = 0; //m_pDoc->getLineStart(start)  m_pDoc
        int linetail = 0; //m_pDoc->getLineEnd(start)
        std::string linestr = ""; //m_pDoc->getLineString(docline)
        s = start - linehead;
        if (s < 0) s = 0;
        e = linetail - end;
        if (e < 0) e = linetail;
        else e = end - linehead;

        const char *p = linestr.c_str();
        //GSize sz = pGraph->getFont().getStringSize(p, linestr.length());
        const int lineHeight =  pGraph->getFont().getHeight();
        int y = startScrline * lineHeight;
        int xs = pGraph->getFont().getStringSize(p, s).w;
        int xe = pGraph->getFont().getStringSize(p, e).w;
        GRect rc;
        rc.setWidth(xe-xs);
        rc.setHeight(lineHeight);
        rc.translate(xs,y);
        dprint(GTEXT("Raw Selection Rect(%d):Pos(%d,%d),Size(%d,%d)\n"), endScrline-startScrline, rc.left,rc.right, rc.width(), rc.height());
        pGraph->fillRect(rc, GColor(0x4e,0x72,0xb8)); //#4e72b8

        startScrline++;
    }
 */   
    int sdocline = startDocline;
    char  lineBuffer[2048]; // Document::getLineString(sdocline)
    
    for ( ; sdocline <= endDocline; sdocline++)
    {
        
        int scrLine = screenLine(sdocline);
        if (scrLine < 0 || scrLine >= GView::getVLinesOnScreen()  ) continue; //invisible

        int linehead = m_pDoc->LineStart(sdocline); //  Document::getLineStart(sdocline)
        int linetail = m_pDoc->LineEnd(sdocline); //  Document::getLineEnd(sdocline) 
        int lineLen = linetail - linehead;
        m_pDoc->GetCharRange(lineBuffer, linehead, lineLen);
        lineBuffer[lineLen] = 0;

        bool drawEnd = true;
        s = start - linehead;
        if (s < 0) s = 0;
        e = linetail - end;
        if (e < 0)
            e = linetail - linehead; // linetail before end
        else {
            e = end - linehead;
            drawEnd = false;
        }
        
        const char *p = lineBuffer;
        //GSize sz = pGraph->getFont().getStringSize(p, linestr.length());
        const int height =  lineHeight();
        int y = scrLine * height;
        int xs = pGraph->getFont().getStringSize(p, s).w;
        int xe = pGraph->getFont().getStringSize(p, e).w;

        if (y < 0 || y > getClientSize().h) return;
        if (xs < 0) xs = 0;
        if (xe > getClientSize().w) xe = getClientSize().w;
        if (xs > xe) return;

        GRect rc;
        rc.setWidth(xe-xs);
        rc.setHeight(height);
        rc.translate(xs,y);
        GRect margin(marginsArea());
        int offset = margin.width() - GView::getHorzPos() * GView::getHorzThumb() ;
        rc.translate(offset, 0);
        if (rc.left < margin.width())
            rc.left = margin.width();
        if (drawEnd)
            rc.right += charWidth();
        if (rc.isValid())
            pGraph->fillRect(rc, GColor(51,153,255)); //#4e72b8 

    }
    
}

void GEdit::_drawLine(Graphics* pGraph, int docLine, int scrRow) const
{
    GFont& font = pGraph->getFont();

    int lineHeight = this->lineHeight();
    GRect rcline(0,0, getClientSize().w, lineHeight);
    rcline.translate(0, scrRow * lineHeight);
    int x = 0;
    int pixelsOffset = 0;
    if (margin_LineNumber)
    {
        pixelsOffset += getMargin(LineNumber, reinterpret_cast<long>(pGraph));
        int margin = getMargin(Margin);
    //    pGraph->fillRect(GRect(x, rcline.top, pixelsOffset + margin, lineHeight), GColor(192,192,192));
        char numberstr[10]=""; // docline -> numberstr
        int len = num2string(docLine + 1, numberstr);
        x = pixelsOffset  - font.getStringSize(numberstr, len).w;
        pGraph->drawString(x,rcline.top, numberstr, len); //, GColor(220,0,0)
        pixelsOffset += margin;
    }
    if (margin_BookMark)
    {
        int x = pixelsOffset;
        const int d = getMargin(BookMark);
        GRect rc(x, rcline.top, d, lineHeight);
        //pGraph->fillRect(rc, GColor(180,180,123));
        GRect bookmk(0,0, d,d);
        bookmk.setCenter(rc.center());
        //pGraph->drawEllipse(bookmk, GColor(123,123,54),GColor(128, 0,0));
        pixelsOffset += d;
    }
    if (margin_Fold)
    {
        GRect rc(rcline);
        rc.left = pixelsOffset;
        const int d = getMargin(Folder);
        rc.setWidth(d);
        pGraph->fillRect(rc, GColor(235,235,235));
        pixelsOffset += d;
    }

    char linestr[4096];
    int start = m_pDoc->LineStart(docLine);
    int slen = m_pDoc->LineEnd(docLine) - start;
    m_pDoc->GetCharRange(linestr, start, slen);
    linestr[slen] = 0;

    int pos = GView::getHorzPos() * GView::getHorzThumb() ;

    rcline.left = pixelsOffset;
    pGraph->drawClipString(GPoint(pixelsOffset - pos, rcline.top), linestr, slen, rcline);
}

//////////////////////////////////////////////////////////////////////////

int GEdit::bottomLine() const 
{
    return visualDocline(topLine(), GView::getVLinesOnScreen() );
}


//////////////////////////////////////////////////////////////////////////
// Document and Screen

inline int GEdit::totalLines() const 
{
    return m_pDoc->LinesTotal();
}

int GEdit::documentLine(const GPoint& point) const 
{
    return visualDocline(topLine(), GView::getScreenRow(point) );
}

int GEdit::documentPos(const GPoint& point)const  
{
    int row = documentLine(point);
    int pixlen = 0;
    GRect rcText(textArea());
    int x = point.x - rcText.left;  // in text rect
    x += GView::getHorzPos() * GView::getHorzThumb(); // 

    const GFont& font = m_pGraphics->getFont();
    int linehead = m_pDoc->LineStart(row);
    int len = m_pDoc->LineEnd(row) - linehead;
    char linestr[4096];
    m_pDoc->GetCharRange(linestr, linehead, len);
    linestr[len] = 0;
//    int slen = len;
//     while ( pixlen = font.getStringSize(linestr, len).w, x < pixlen) len--;
//     if ( len < slen - 1)
//     {
//         int mx = (font.getStringSize(linestr, len + 1).w + pixlen) / 2;
//         if (x > mx) len++;
//     }
    const char* p = linestr;
    int s = 0, slen;
    int l = 0;
    while(*p  && pixlen < x)
    {
        s = p - linestr;
        slen = pixlen;
        p = next_character(p);
        l = p - linestr;
        pixlen = font.getStringSize(linestr, l).w;
    }
    int mx = (slen + pixlen) / 2;
    if (x <= mx) l = s;
    
    return linehead + l;
}

GPoint GEdit::screenPoint( int docPos) const 
{
    GPoint pt(-1, -1);
    int docline = m_pDoc->LineFromPosition(docPos); 
    int scrline = screenLine(docline); // 
    pt.y = lineHeight() * scrline;

    char linestr[2048];
    int pos = m_pDoc->LineStart(docline);
    int len = m_pDoc->LineEnd(docline) - pos;
    m_pDoc->GetCharRange(linestr,pos, len);
    linestr[len] = 0;
    const GFont& font = m_pGraphics->getFont();
    pt.x = marginsArea().width()  + font.getStringSize(linestr, docPos - pos).w; // offset pixels of chars
    pt.x -= GView::getHorzPos() * GView::getHorzThumb();

    return pt;
}



int GEdit::screenLine(int docLine) const 
{
    int row = 0;
    int top = topLine();
    int counts = GView::getVLinesOnScreen();
    while (top <= docLine && row < counts)
    {
        if (isVisible(top)) row++;
        top++;
    }
    if ( top <= docLine) // selected line is under the last visible one
    {
        row = counts + 1;
    }
    return row - 1;
}

void GEdit::setCaretAt(int docPos)
{ 
    if (m_ptCaretDocPos == docPos) 
        return ;
    m_ptCaretDocPos = docPos;
    
    GRect rcText(textArea());
    if (!rcText.isValid()) return;  // sometimes textArea is not invalid, such as initializing...
    GPoint pt;
    while(pt = screenPoint(docPos), !rcText.contains(pt))  //make sure caret is visible
    {
        int pos = GView::getHorzPos();
        if (pt.x < rcText.left){
            GView::scrollTo(HSCROLL, pos - 2);
        }else if (pt.x > rcText.right){
            GView::scrollTo(HSCROLL, pos + 2);
        }

        pos = GView::getVertPos();
        if (pt.y < rcText.top){
            GView::scrollTo(VSCROLL, pos - 1);
        }else if (pt.y > rcText.bottom){
            GView::scrollTo(VSCROLL, pos + 1);
        }
    }
    m_pCaret->setPos(pt);
}

inline bool GEdit::isVisible(int docline) const
{
    return true;
}

int GEdit::visualDocline(int docline, int offset /* = 1 */) const
{
    int t = docline;
    int total = totalLines();
    int count = offset;
    int d = 1;
    if (offset < 0)
    {
        count = -offset;
        d = -1;
    }
    for ( int i = 0; i < count; )
    {
        t += d;
        if (isVisible(t)) i++;
        if (t < 0){ t = 0; break; }
        if (t >= total) { t = total - 1; break;}
    }
    return t;
}

GSize GEdit::stringSize(const GFont& font, const char* str, int len) const
{
    int x = 0;
    int y = 0;
    const int space_width = font.getCharSize(' ').w;
    int tab_size = m_pDoc->tabInChars;
    for (const char*p = str; p - str < len; p++)
    {
        switch(*p)
        {
        case '\n': x += 0; break;
        case '\r': break;
        case '\t': x += tab_size * space_width; break;
        default:
            x += font.getCharSize(*p).w;
        }
    }

    return GSize(x, y);
}

GSize GEdit::stringSize(const GFont& font, const GString& str) const
{
    int x = 0;
    int y = 0;
    const int space_width = font.getCharSize(' ').w;
    const gchar* pstr = str.c_str();
    for (const gchar*p = pstr; (size_t)(p - pstr) < str.length(); p++)
    {
        switch(*p)
        {
        case '\n': x += space_width; break;
        case '\r': break;
        case '\t': x += 4 * space_width; break;
        default:
            x += font.getTextSize(GString(p, 1)).w;
        }
    }

    return GSize(x, y);
}

void GEdit::zoom(int z)
{
    if (z == 0) return;

    GFont::FontProperty prop;
    const GFont& font = m_pGraphics->getFont();
    font.getProperty(prop);
    uchar& size = prop.size;
    size += z;
    if (size < 9) size = 9;
    else if (size > 60) size = 60;
    m_pGraphics->setFont(font.getFontName(), prop.size);
    setLineHeight(font.getHeight());
    m_pCaret->setSize(GSize(1,lineHeight()));
    GView::setViewScroll(VSCROLL, GView::getVertPos(), totalLines());
}

void GEdit::selectAll()
{
    int len = m_pDoc->Length();
    m_selection.setStart(0);
    m_selection.setEnd(len);
    repaint();
}